<?php
/* 
 * Miscellaneous functions.
 */

require_once('lib.php');
require_once('data_access.php');

/**
 * Stream PNG image to client browser. First the image is looked for in the file
 * cache, failing that it's read from the database.
 * @param string $img_id Image record ID.
 * @param string $cache_ext Extension to cached filename to differentiate
 * transformed image from original image (eg '_80' for thumbnail).
 * @param function $transform Function to apply a transform to an image
 * resource before it's cached (f(&image_resource)). Creating a thumbnail is a
 * typical example.
 */
function stream_cachable_img($img_id, $cache_ext='', $transform=null) {
    global $_CONFIG;
    header("Content-type: image/png");
    $file_name = $_CONFIG->image_cache . '/' . $img_id . $cache_ext;

    // stream image from file cache if it exists
    if (file_exists($file_name)) {
        if (!$img = imagecreatefrompng($file_name)) {
            // corrupt image in file cache so fail but delete file so that
            // image will be recreated in cache on next request
            unlink($file_name);
            exit;
        }

    // read image from database, add to file cache, then steam to client
    } else {
        if (!$img_record = read_image($img_id)) {
            http_not_found();
        }
        if (!$img = imagecreatefromstring($img_record['data'])) {
            exit; // corrupt image data in database!
        }
        // apply optional transform to image before storing it in cache
        // (eg generate thumbnail)
        if ($transform) {
            $transform($img);
        }
        imagepng($img, $file_name); // store image in file cache
    }

    // stream to client then destroy
    imagepng($img);
    imagedestroy($img);
}

/**
 * Validate posted form field values.
 * @param array $constraints Assoc array of form fields, each with a function
 * that applies contraints to the form value and returns null for success, or
 * a string containing the reason for failure.
 * @return array Assoc array of fields that failed their constraints and the
 * reason for failure (field => reason_for_failure).
 */
function validate_form_post($constraints) {

    // check all expected form fields have been submitted
    if (!all_keys_present($_POST, array_keys($constraints))) {
        http_bad_request();
    }

    // return array of fields failing their contraint
    $errors = array();
    foreach ($constraints as $field => $constraint_func) {
        $error = $constraint_func();
        if ($error) {
            $errors[$field] = $error;
        }
    }
    return $errors;
}

/**
 * Output two arrays, each containing a key for every field in a form. First
 * array contains values, from a previous form submission, that have been
 * made safe to render. Second array contains HTML formatted error messages
 * for each field that failed a constraint.
 *
 * The format of the two output arrays is designed to make it simple to
 * interpolate the field values and errors into an HTML form during rendering.
 *
 * @param array $fields Field names belonging to form.
 * @param array $errors Assoc array of errors from previous form submission
 * (field_name => error_msg).
 * @param array $values Assoc array of raw, unsafe form values from previous
 * submission (field_name => raw_value).
 * @param array $formatted_errors Filled with a key for each form field. The
 * associated value will either be null (if there was no error) or the error
 * message expressed as HTML.
 * @param array $formatted_values Filled with a key for each form field. The
 * associated value will be an HTML-safe version of the previously submitted
 * value.
 */
function format_form_fields($fields, $errors, $values,
                            &$formatted_errors, &$formatted_values) {
    foreach ($fields as $field) {
        $formatted_values[$field] = isset($values[$field]) ?
            htmlentities($values[$field]) : null;
        $formatted_errors[$field] = isset($errors[$field]) ?
            "<div class=\"error\">$errors[$field]</div>" : null;
    }
}

/**
 * Print the candidate section of the standard page, showing either 3
 * candidates to be voted on, or an unavailable message.
 * @param array list of 3 candidate arrays
 */
function print_candidates($candidates) {
    if (!$candidates) {
        // will only really happen during simulations
        print "<br />";
    } else {
        $date = date('M Y', read_this_month());
        print "<div class=\"investment-list-item\">";
        print_html_blog_summary();
        print "<div class=\"time-and-amount\">$date<em>£100</em></div></div>";
        foreach ($candidates as $candidate) {
            print_html_investment_short($candidate);
        }
        print "<div class=\"investment-list-item-proposal-end\"></div>";
    }
}

/**
 * Get an image resource representing a scaled-down, cropped version of an
 * existing image resource.
 * @param array $img Image resource of original image.
 * @param int $thumb_size height and width of desired thumbnail.
 * @return object Image resource of scaled-down, cropped image.
 */
function thumbnail($img, $thumb_size) {

    // get original image size
    $original_width = imagesx($img);
    $original_height = imagesy($img);

    // create 2 image GDs; the original, and the cropped
    $cropped_img = imagecreatetruecolor($thumb_size, $thumb_size);

    // crop and scale image to produce thumbnail
    if ($original_width > $original_height) {

        // landscape
        $adjusted_width = $original_width / ($original_height / $thumb_size);
        $dest_x = ($adjusted_width / 2) - ($thumb_size / 2);
        imagecopyresampled($cropped_img, $img, -$dest_x, 0, 0, 0,
                           $adjusted_width, $thumb_size, $original_width,
                           $original_height);
    }
    else {

        // portrait or square
        $adjusted_height = $original_height / ($original_width / $thumb_size);
        $dest_y = ($adjusted_height / 2) - ($thumb_size / 2);
        imagecopyresampled($cropped_img, $img, 0, -$dest_y, 0, 0, $thumb_size,
                           $adjusted_height, $original_width, $original_height);
    }

    // free original image GD
    imagedestroy($img);
    return $cropped_img;
}

/**
 * Return the number of months that application has been operational for.
 * @return int number of months
 */
function get_timeframe() {
    global $_CONFIG;
    $start = strtotime($_CONFIG->investment_start);
    $end = read_this_month();
    return months_since_year_0($end) - months_since_year_0($start);
}

/**
 * Return cassandra key representing data associated with a specific month.
 * @param timestamp month
 * @return string cassandra key (which may not necessarily exist)
 */
function month_to_cassandra_key($month) {
    return date('Ym', $month);
}

/**
 * Write JSON response to Ajax GET/POST request.
 * @param array $response Assoc array of HTML to be used to update DIV
 * containers whose IDs match array keys. Use * prefix in target DIV ID to
 * prepend HTML, * suffix to append HTML and neither to replace all target
 * DIV content.
 */
function ajax_response($response) {
    foreach ($response as $target => $html) {

        // resolve client-side update method from target DIV ID syntax
        if ($target[0] == '*') {
            $target = substr($target, 1);
            $method = 'prepend';
        } else if (substr($target, -1) == '*') {
            $target = substr($target, 0, strlen($target)-1);
            $method = 'append';
        } else {
            $method = 'update';
        }

        // build response array element
        $output[] = array(
            'target' => '#'.$target,
            'method' => $method,
            'html' => $html
        );
    }
    print json_encode($output);
}

/**
 * Capture and return as string all output generated by a function
 * that would otherwise be written to standard output.
 * @param string $function Function to invoke.
 * @param array $args Arguments to be passed to function.
 * @return string Function printed output.
 */
function capture_print($function, $args=array()) {
    ob_start();
    call_user_func_array($function, $args);
    $contents = ob_get_contents();
    ob_end_clean();
    return $contents;
}

?>
